<?php
/**
 *  @mainpage CxSS
 *  CxSS is a ... based on PHP5 DOM extension.
 * 
 *  @section intro_sec Introduction
 *  This is the introduction.
 * 
 *  @section weblinks Project Weblinks
 *  @par Project host
 *  http://code.google.com/p/cxss/
 *  @par SVN
 *  svn checkout http://cxss.googlecode.com/svn/trunk/ cxss-read-only
 * 
 *  @subsection step1 Step One
 *  Two
 *  Three 
 * 
 *  etc...
 * 
 */

//+----------------------------------------------------------------------------+
//+    CxSS
//+----------------------------------------------------------------------------+

class CxSS
{
    /**
     *  Try to apply $callback($arg1, $arg2, *) to $mixed.
     *  @param $callback 
     *  @param $mixed 
     * 
     *  @code
     *      function callback ($mix, $arg0, $arg2, ...) /.../
     *      $callback = 'callback';
     *   --- OR ---
     *      class C / function callback ($mix, $arg0, $arg2, ...) /.../
     *      $obj = new C();
     *      $callback = array($obj, 'callback');
     *  @endcode
     *  @return $callback($mixed, $args[0], $args[1], ...)
     * 
     *  Special case:
     *  @code
     *      class M function method ($mix, $arg0, $arg2, ...) /.../
     *      $mixed = new M();
     *      $callback = array('method');
     *  @endcode
     *  @return $mixed->{$callback}($args[0], $args[1], ...)
     * 
     */
    static function apply ($callback, $mixed)
    {
        $args = func_get_args();
        array_shift($args);//$callback
        array_shift($args);//$mixed
        return self::apply_mixed($callback, $mixed, $args);
    }

    /**
     *  Try to apply $callback($args) to all the $mixed.
     *  @param $callback callback
     *  @param $mixed any
     *  @param $args array
     *  @retval any
     *  @retval array if $mixed is array
     */
    static function apply_mixed ($callback, $mixed, $args = array())
    {
        if (!$mixed) return null;
        if (is_array($mixed) || is_object/*???*/($mixed)) {
            $result = array();
            foreach ($mixed as $item) {
                $value = self::apply_once($callback, $item, $args);
                if (isset($value)) $result[] = $value;
            }
            return $result;
        }
        else {
            return self::apply_once($callback, $mixed, $args);
        }
    }

    /**
     *  Try to apply $callback($args) to $item once.
     *  @param $callback callback
     *  @param $item any
     *  @param $args array
     *  @return any 
     */
    static function apply_once ($callback, $item, $args = array())
    {
        if (is_scalar($args)) $args = array($args);
        if (is_array($callback) && count($callback) == 1) {
            array_unshift($callback, $item);
            if (!is_callable($callback))
                //  skip the not applicable call
                return null;
        }
        else {
            array_unshift($args, $item);
        }
        return call_user_func_array($callback, $args);
    }

    /**
     *  Return argument's type name.
     *  @param $var any
     *  @return string 
     */
    static function typename ($var)
    {
        return is_object($var) ? get_class($var) : gettype($var);
    }

}

//+----------------------------------------------------------------------------+
//+    ExAbstract
//+----------------------------------------------------------------------------+

abstract class ExAbstract extends ErrorException
{
    protected abstract function ename ();
    public function __get ($name)
    {
        return $this->{$name};
    }

}

//+----------------------------------------------------------------------------+
//+    ExError
//+----------------------------------------------------------------------------+

class ExError extends ExAbstract
{
    protected function ename ()
    {
 return 'Error';
    }

    protected function topic ()
    {
 return $this->message;
    }

    /**
     *  Intercept system errors and throw them as exceptions.
     *  @param $mask  = E_RECOVERABLE_ERROR 
     *  @return void
     */
    static function throw_system_errors ($mask = E_RECOVERABLE_ERROR)
    {

        set_error_handler(create_function('$a, $b, $c, $d',
            'throw new ' .__class__ .'($b, 0, $a, $c, $d);'), $mask);
    }

    /**
     *  @param $e Exception 
     *  @return void 
     */
    static function write ($e)
    {
echo <<<END

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

$e
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

END;
    }

    function __toString ()
    {
        $message  = "{$this->ename()}. {$this->topic()}";
        ///@todo check severity
        $message .= " in {$this->file}({$this->line}).\n";
        $message .= "{$this->getTraceAsString()}\n";
        return $message;
    }

}

//+----------------------------------------------------------------------------+
//+    ExNotApplicable
//+----------------------------------------------------------------------------+

class ExNotApplicable extends ExError
{
    function __construct ($func = 'operation', $arg = null, $code = null, $severity = null)
    {
        if (isset($arg)) $func .= " to ".CxSS::typename($arg);
        parent::__construct($func, $code, $severity);
    }

    protected function topic ()
    {

        return "Not applicable {$this->message}";
    }

}

//+----------------------------------------------------------------------------+
//+    ExNotImplemented
//+----------------------------------------------------------------------------+

class ExNotImplemented extends ExError
{
    function __construct ($func = 'feature', $code = null, $severity = null)
    {
        parent::__construct($func, $code, $severity);
    }

    protected function topic ()
    {

        return "Not implemented {$this->message}";
    }

}

//+----------------------------------------------------------------------------+
//+    ExTODO
//+----------------------------------------------------------------------------+

class ExTODO extends ExNotImplemented
{
    protected function topic ()
    {

        return "@TODO: {$this->message}";
    }

}

//+----------------------------------------------------------------------------+
//+    CxIni
//+----------------------------------------------------------------------------+

class CxIni implements ArrayAccess
{
    /**
     *  Constructs the ini-object and processes it 
     *  if $ini is not null (see CxIni::process()).
     *  @param $ini  = null
     */
    function __construct ($ini = null)
    {

        $ini && $this->process($ini);
    }

    /**
     * 
     *  Processes $INI parameter.
     *  @param $ini array   to process it by dispatching
     *                      section definitions to handlers.
     *  @param $ini string  to load ini-file and processes it.
     */
    function process ($ini)
    {
        if (is_string($ini)
        && !$ini = parse_ini_file($ini, true))
            return false;
        foreach ($ini as $section => $entry) {
            $method = "section_$section";
            if (method_exists($this, $method)) {
            	if (is_array($entry)) {
                    foreach ($entry as $key => $val) {
                        $this->{$method}($key,
                            $this->expr($val)
                        );
                    }
                }
                else {
                    $this->{$method}(null,
                            $this->expr($entry)
                    );
                }
            }
            else {
            	if (is_array($entry)) {
                    foreach ($entry as $key => $val) {
                        $this->store($section, $key,
                            $this->expr($val)
                        );
                    }
                }
                else {
                    $this->store_value($section,
                            $this->expr($entry)
                    );
                }
            }
        }
        return $this;
    }

    private $_data_ = array();  //  array

    function data ()
    {

        return $this->data;
    }

    function reset ($data = array())
    {

        $this->_data_ = $data;
        return $this;
    }

    /**
     *  Stores section value.
     *  @param $section 
     *  @param $key 
     *  @param $val 
     *  @return void
     */
    protected function store ($section, $key, $val)
    {

        $this->_data_[$section][$key] = $val;
    }

    /**
     *  Stores root (non-section) value.
     *  @param $key 
     *  @param $val 
     *  @return void
     */
    protected function store_value ($key, $val)
    {

        $this->_data_[$key] = $val;
    }

    /**
     *  Returns $val, or eval($val) if $val[0] == '='.
     *  @param $val 
     *  @return any
     */
    protected function expr ($val)
    {
        //  SHOULD NOT BE STATIC  //
        return is_string($val)
            && strlen($val) > 1
            && @$val[0] == '='
            ? eval("return \$_$val;") : $val;
    }

    /**
     *  Defines $KEYs with $VALues.
     *  @code
     *  [define]
     *  key = val
     *  ...
     *  @endcode
     */
    protected static function section_define ($key, $val)
    {

        define($key, $val);
    }

    /**
     *  Processes $val as ini-file and merges it with data already stored.
     *  @param $key 
     *  @param $val 
     *  @return void
     */
    protected function section_include ($key, $val)
    {
        $ini = get_class();
        $ini = new $ini($val);
        $this->_data_ = //!isset($key) ? $ini->_data_ :
            array_merge($ini->_data_, $this->_data_);
    }

    /**
     *  Together with ArrayAccess Implementation makes
     *  $INI[section][key] syntax possible.
     */
    function offsetGet ($name)
    {

        return $this->_data_[$name];
    }

    function offsetExists ($name)
    {

        return isset($this->_data_[$name]);
    }

    function offsetSet ($name, $value)
    {

        //return $this->_data_[$name] = $value;
        throw new ExNotApplicable(
            get_class($this)."[$name]=".gettype($value)
        );
    }

    function offsetUnset ($name)
    {

        //unset($this->_data_[$name]);
        throw new ExNotApplicable(
            'unset('.get_class($this)."[$name])"
        );
    }

    function __call ($function, $args)
    {
        return call_user_func_array($this->{$function},$args);
    }

}

//+----------------------------------------------------------------------------+
//+    CxSession
//+----------------------------------------------------------------------------+

class CxSession
{
    static function check ($force = false)
    {
        if (isset($_COOKIE[session_name()]) || $force)
            session_start();
    }

    static function is_started ()
    {
        return isset($GLOBALS['_SESSION']);
    }

    static function close ($cond = true)
    {
        if (!$cond) return false;
        $scp = session_get_cookie_params();
        setcookie(session_name(), '', 1/*time()-12*3600*/, $scp['path']);
        if (!self::is_started()) return null;
        $_SESSION = array();
        @session_destroy();
        return true;
    }

    static function varc ($name, $callback)
    {
        if (isset($_SESSION[$name])) {
            return $_SESSION[$name];
        }
        else {
            $args = func_get_args();
            return $_SESSION[$name] = call_user_func_array(
                $callback,
                array_slice($args, 2)
            );
        }
    }

    static function varv ($name, $var)
    {
        if (isset($_SESSION[$name])) {
            return $_SESSION[$name];
        }
        else {
            return $_SESSION[$name] = $var;
        }
    }

}
?>